{This file is part of CheLive.

CheLive is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Foobar is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Foobar.  If not, see <http://www.gnu.org/licenses/>.}

{Реализует работу с FBO (объектами кадрового буфера)}
unit CoreFBO;

interface

uses
	gl, glext, CoreTypes, CoreTexture;

const
	FBO_2D = 0;
	FBO_CUBE_PX = 1;
	FBO_CUBE_NX = 2;
	FBO_CUBE_PY = 3;
	FBO_CUBE_NY = 4;
	FBO_CUBE_PZ = 5;
	FBO_CUBE_NZ = 6;

type
	{FBO (объект кадрового буфера)
	@member(frame идентификатор кадрового буфера)
	@member(depth идентификатор рендер-буфера глубины)
	@member(w ширина)
	@member(h высота)
	@member(colorTex присоединенная текстура цвета)
	@member(depthTex присоединенная текстура глубины)}
	TFBO = class(TListItem)
		frame, depth: TGLint;
		w, h: Integer;
		colorTex, depthTex: TTexture;

		{Инициализация
		@param(color если False то буфер цвета будет выключен)
		@param(depth_ если False то буфер глубины будет выключен)
		@param(colorTex_ присоединяемая текстура цвета)
		@param(depthTex_ присоединяемая текстура глубины)
		@returns(True если FBO инициализирован)}
		function Init(color, depth_: Boolean; colorTex_: TTexture = nil; depthTex_: TTexture = nil): Boolean;
		{Включение буфера для отрисовки в него}
		procedure Enable(target: Integer = FBO_2D);
		{Выключение буфера}
		class procedure Disable;

		constructor Create;
		destructor Destroy; override;
	end;

implementation

uses
	CoreConsole, CoreWindow;

constructor TFBO.Create;
begin
	frame := 0;
	depth := 0;
end;

destructor TFBO.Destroy;
begin
	if frame <> 0 then glDeleteFramebuffersEXT(1, @frame);
end;

function CheckStatus(fbo: TGLint): Boolean;
var
	status: TGLenum;
	error: String;
begin
	Result := True;

	status := glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);

	case status of
		GL_FRAMEBUFFER_COMPLETE_EXT: Exit;
		GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: error := 'Framebuffer incomplete: Attachment is NOT complete';
		GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: error := 'Framebuffer incomplete: No image is attached to FBO';
		GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: error := 'Framebuffer incomplete: Attached images have different dimensions';
		GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: error := 'Framebuffer incomplete: Color attached images have different internal formats';
		GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT: error := 'Framebuffer incomplete: Draw buffer';
		GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT: error := 'Framebuffer incomplete: Read buffer';
		GL_FRAMEBUFFER_UNSUPPORTED_EXT: error := 'Unsupported by FBO implementation';
		else error := 'Unknow error';
	end;

	con.Msg('#Не удается создать FBO: %s', [error]);

	Result := False;
end;

function TFBO.Init(color, depth_: Boolean; colorTex_: TTexture = nil; depthTex_: TTexture = nil): Boolean;
begin
	Result := False;

	colorTex := colorTex_;
	depthTex := depthTex_;

	if colorTex <> nil then
	begin
		w := colorTex.w;
		h := colorTex.h;
	end else

	if depthTex <> nil then
	begin
		w := depthTex.w;
		h := depthTex.h;
	end else

	begin
		con.Msg('# Не удается создать FBO: нужно прикрепить хотя бы одну текстуру');
		Exit;
	end;

	glGenFramebuffersEXT(1, @frame);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame);

	if depth_ then
	begin
		glGenRenderbuffersEXT(1, @depth);
		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth);
		glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h);
		glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
		glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth);

		if depthTex <> nil then
		begin
			glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depthTex.id, 0);
		end;
	end;

	if color then
	begin
		if colorTex <> nil then
		begin
			glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, colorTex.id, 0);
		end;
	end else
	begin
		glDrawBuffer(GL_NONE);
		glReadBuffer(GL_NONE);
	end;

	if not CheckStatus(frame) then
	begin
		//error
		Exit;
	end;

	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

	Result := True;
end;

function FBOTargetToGL(target: Integer): GLenum;
begin
	case target of
		FBO_2D: Result := GL_TEXTURE_2D;
		FBO_CUBE_PX: Result := GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB;
		FBO_CUBE_NX: Result := GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB;
		FBO_CUBE_PY: Result := GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB;
		FBO_CUBE_NY: Result := GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB;
		FBO_CUBE_PZ: Result := GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB;
		FBO_CUBE_NZ: Result := GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB;
	end;
end;

procedure TFBO.Enable(target: Integer = FBO_2D);
begin
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, frame);
	glViewport(0, 0, Round(w), Round(h));
	
	if colorTex <> nil then
	begin
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, FBOTargetToGL(target), colorTex.id, 0);
	end;
end;

class procedure TFBO.Disable;
begin
	glViewport(0, 0, Round(wnd.clientSize.x), Round(wnd.clientSize.y));
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
end;

end.
